package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/satori/go.uuid"
	"net/http"
	"strings"
	"time"

	"github.com/gorilla/mux"
)

const (
	STATUS_TASK_STARTED = 1
	STATUS_TASK_RUNNING = 2
	STATUS_TASK_DONE    = 3
	STATUS_TASK_FAILED  = 4
)

type Task struct {
	ID     string `json:"id"`
	Result string `json:"data"`
	Status int    `json:"status"`
	Remark *string `json:"remark"`
}

type JSONResult struct {
	Status int         `json:"status"`
	Error  string      `json:"error"`
	Data   interface{} `json:"data"`
}

type GetTaskResult struct {
	JSONResult
	Task *Task `json:"task"`
}

type OSSCallbackResult struct {
	Status string `json:"Status"`
}

type TaskResult struct {
	JSONResult
	TaskID string `json:"task_id"`
}

type HTTPService struct {
	rootConfig *PlistConfig
	config     *HttpConfig
	tasks      map[string]*Task
	taskLock   chan bool
	wechatAuth *WeChatWorkUser
	http       *http.Server
}

func NewHTTPService(conf *PlistConfig) *HTTPService {
	return &HTTPService{
		config:     conf.Web,
		rootConfig: conf,
		tasks:      make(map[string]*Task),
		taskLock:   make(chan bool, 1),
		wechatAuth: NewWeChatWorkUser(conf.WeChat.WeChatWork),
	}
}

func (this *HTTPService) StartTask() *Task {
	this.taskLock <- true
	defer func() {
		<-this.taskLock
	}()

	task := &Task{
		ID: fmt.Sprintf("%s", uuid.NewV4()),
		Status: STATUS_TASK_STARTED,
	}

	return task
}

func (this *HTTPService) UpdateTaskStatus(uuid string, task *Task) {
	this.taskLock <- true
	defer func() {
		<-this.taskLock
	}()

	this.tasks[uuid] = task
}

func (this *HTTPService) RemoveTask(uuid string) {
	this.taskLock <- true
	defer func() {
		<-this.taskLock
	}()

	delete(this.tasks, uuid)
}

func (this *HTTPService) SetTaskFailed(task *Task) {
	task.Status = STATUS_TASK_FAILED
	this.UpdateTaskStatus(task.ID, task)
}

func (this *HTTPService) Start() {
	r := mux.NewRouter()

	if this.rootConfig.Auth {
		r.Use(this.wechatAuth.AuthMiddleware(`/ui`))
	}
	r.HandleFunc("/auth/wechat/work/callback", this.wechatAuth.handleAuthCallBack)
	r.HandleFunc("/upload", this.UploadPlistFromURL)
	r.HandleFunc("/task", this.GetTask)
	r.HandleFunc("/oss/upload", this.GetOSSUploadParam)
	r.HandleFunc("/oss/callback.json", this.OSSWebUploadCallBack)
	r.HandleFunc("/oss/callback/url", this.DebugOSSCallBackURL)
	r.HandleFunc("/oss/submit", this.OSSAssetSubmit)
	r.PathPrefix("/swagger/").Handler(http.StripPrefix("/swagger/",
		http.FileServer(http.Dir(fmt.Sprintf("%s/swagger", this.config.WebRoot)))))
	r.PathPrefix("/ui/").Handler(http.StripPrefix("/ui/",
		http.FileServer(http.Dir(fmt.Sprintf("%s/ui", this.config.WebRoot)))))
	r.HandleFunc("/", this.RedirectUI)
	r.NotFoundHandler = http.HandlerFunc(this.NotFoundHandle)

	log.Info("http service starting")
	log.Infof("Please open http://%s\n", this.config.Listen)
	this.http = &http.Server{
		Addr:    this.config.Listen,
		Handler: r,
	}
	this.http.ListenAndServe()
}

func (this *HTTPService) Stop() error {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	return this.http.Shutdown(ctx)
}

func (this *HTTPService) ResponseJSON(source interface{}, writer http.ResponseWriter) {
	writer.Header().Set("Content-Type", "application/json")
	encoder := json.NewEncoder(writer)
	err := encoder.Encode(source)
	if err != nil {
		this.ResponseError(err, writer)
	}
}

func (this *HTTPService) ResponseError(err error, writer http.ResponseWriter) {
	server_error := &JSONResult{Error: err.Error(), Status: 500}
	json_str, _ := json.Marshal(server_error)
	http.Error(writer, string(json_str), 500)
}

func (this *HTTPService) NotFoundHandle(writer http.ResponseWriter, request *http.Request) {
	http.Error(writer, `{"status": 404, "msg":"handle not found!"}`, 404)
}

func (this *HTTPService) RedirectUI(writer http.ResponseWriter, request *http.Request) {
	http.Redirect(writer, request, "/ui/index.html", 301)
}

func (this *HTTPService) UploadPlistFromURL(writer http.ResponseWriter, request *http.Request) {
	link := request.FormValue("url")
	email := request.FormValue("email")

	task := this.StartTask()

	time.AfterFunc(1*time.Hour, func() {
		this.RemoveTask(task.ID)
	})

	sender := NewEmailSender(this.rootConfig.SendGrid)

	go func() {
		task.Status = STATUS_TASK_RUNNING
		task.Remark = String(`loading plist info`)
		this.UpdateTaskStatus(task.ID, task)
		generator := NewGenerator(this.rootConfig)

		timer := NewTimer(time.Second * 2)
		timer.Start(func() {
			task.Remark = String(generator.ReadLog().String())
		})
		defer func() {
			timer.Stop()
			timer.Dispose()
		}()

		handler, err := generator.LoadPlistURL(link)
		if err != nil {
			log.Error(err)
			this.SetTaskFailed(task)
			return
		}

		defer func() {
			if len(email) > 0 && err != nil {
				log.Info(`send failed email`)
				err = sender.SendTaskFailedEmail(email, err)
				if err != nil {
					log.Error(err)
				}
			}
		}()

		defer func() {
			if generator != nil {
				log.Info("clear temp data")
				generator.Clear()
			}
		}()


		err = handler.DownloadToLocal()
		if err != nil {
			log.Error(err)
			this.SetTaskFailed(task)
			return
		}

		url, err := handler.UploadAll()
		if err != nil {
			log.Error(err)
			this.SetTaskFailed(task)
			return
		}

		log.Info("upload success, ", url)

		task.Status = STATUS_TASK_DONE
		task.Result = url
		this.UpdateTaskStatus(task.ID, task)

		if len(email) > 0 {
			log.Info(`send completed email`)
			err = sender.SendTaskCompletedEmail(email, url)
			if err != nil {
				log.Error(err)
			}
		}
	}()

	this.ResponseJSON(&TaskResult{
		JSONResult: JSONResult{
			Status: 200,
		},
		TaskID: task.ID,
	}, writer)
}

func (this *HTTPService) GetTask(writer http.ResponseWriter, request *http.Request) {
	TaskID := request.FormValue("id")

	task, ok := this.tasks[TaskID]
	if !ok {
		this.ResponseError(errors.New("task not found"), writer)
		return
	}

	this.ResponseJSON(&GetTaskResult{
		JSONResult: JSONResult{
			Status: 200,
		},
		Task: task,
	}, writer)
}

func (this *HTTPService) GetOSSUploadParam(writer http.ResponseWriter, request *http.Request) {
	filename := request.FormValue("filename")

	oss, err := NewOSSStorage(this.rootConfig.Storage.OSS)
	if err != nil {
		this.ResponseError(err, writer)
		return
	}

	result, err := oss.GenerateFormMultipart(filename, 1*time.Hour, ``)
	if err != nil {
		this.ResponseError(err, writer)
		return
	}

	this.ResponseJSON(&JSONResult{
		Status: 200,
		Data:   result,
	}, writer)
}

func (this *HTTPService) DebugOSSCallBackURL(writer http.ResponseWriter, request *http.Request) {
	this.ResponseJSON(&JSONResult{
		Status: 1,
		Data: this.GetOSSCallbackURL(request),
	}, writer)
}

func (this *HTTPService) GetOSSCallbackURL(request *http.Request) string {

	//log.Info(request.Header)

	url := request.URL
	scheme := request.Header.Get("X-Forwarded-Proto")
	if len(scheme) > 0 {
		if strings.Contains(scheme, ",") {
			schemeList := strings.SplitN(scheme, ",", 2)
			url.Scheme = schemeList[0];
		} else {
			url.Scheme = scheme;
		}
	}
	host := request.Header.Get("X-Forwarded-Host")
	if len(host) > 0 {
		url.Host = host;
	}
 	url.Path = "/oss/callback.json";
	url.RawQuery = "";

	log.Debug(url)

	return url.String();
}

func (this *HTTPService) OSSWebUploadCallBack(writer http.ResponseWriter, request *http.Request) {
	log.Info(request)

	this.ResponseJSON(&OSSCallbackResult{Status: "OK"}, writer)
	return

	res, err := VerifyWebUploadCallback(request)
	if err != nil {
		log.Error(err)
		this.ResponseError(err, writer)
		return
	}

	if res {
		this.ResponseJSON(&OSSCallbackResult{Status: "OK"}, writer)
		return
	}

	this.ResponseError(errors.New("Signature Verification is Failed"), writer)
	return
}

func (this *HTTPService) OSSAssetSubmit(writer http.ResponseWriter, request *http.Request) {
	decoder := json.NewDecoder(request.Body)
	var params []*PlistAsset
	err := decoder.Decode(&params)
	if err != nil {
		this.ResponseError(err, writer)
		return
	}

	queryVal := request.URL.Query()
	email := queryVal.Get("email")

	task := this.StartTask()

	time.AfterFunc(1*time.Hour, func() {
		this.RemoveTask(task.ID)
	})

	sender := NewEmailSender(this.rootConfig.SendGrid)

	go func() {
		task.Status = STATUS_TASK_RUNNING
		this.UpdateTaskStatus(task.ID, task)
		generator := NewGenerator(this.rootConfig)
		log.Info(`generate plist info`)

		timer := NewTimer(time.Second * 2)
		timer.Start(func() {
			tmp := generator.ReadLog().String();
			task.Remark = String(tmp)
		})
		defer func() {
			timer.Stop()
			timer.Dispose()
		}()

		plistURL, _, err := generator.UploadWithAssetList(params)
		if err != nil {
			log.Error(err)
			this.SetTaskFailed(task)
			return
		}

		log.Info(`download plist from url`)
		handler, err := generator.LoadPlistURL(plistURL)
		if err != nil {
			log.Error(err)
			this.SetTaskFailed(task)
			return
		}

		defer func() {
			if len(email) > 0 && err != nil {
				log.Info(`send failed email`)
				err = sender.SendTaskFailedEmail(email, err)
				if err != nil {
					log.Error(err)
				}
			}
		}()

		defer func() {
			if generator != nil {
				log.Info("clear temp data")
				generator.Clear()
			}
		}()

		err = handler.DownloadToLocal()
		if err != nil {
			log.Error(err)
			this.SetTaskFailed(task)
			return
		}

		url, err := handler.UploadAll()
		if err != nil {
			log.Error(err)
			this.SetTaskFailed(task)
			return
		}

		log.Info("upload success, ", url)

		task.Status = STATUS_TASK_DONE
		task.Result = url
		this.UpdateTaskStatus(task.ID, task)

		if len(email) > 0 {
			log.Info(`send completed email`)
			err = sender.SendTaskCompletedEmail(email, url)
			if err != nil {
				log.Error(err)
			}
		}
	}()

	this.ResponseJSON(&TaskResult{
		JSONResult: JSONResult{
			Status: 200,
		},
		TaskID: task.ID,
	}, writer)
}

func String(str string) *string {
	return &str
}